﻿using System;
using System.Collections.Generic;
using Internal.KuiHuaBaoDian.Services.Avatar;
using UnityEngine;
using UnityEngine.Assertions;

namespace KuiHuaBaoDian.Services.Avatar {

    public class AvatarPartHandle {

        public Guid GUID { get; } = Guid.NewGuid();
    }

    public interface IAvatar {

        void Initialize(GameObject skeletonGameObject);

        AvatarPartHandle AddPart(GameObject partGameObjec, AvatarAdditionalPartSetting additionalPartSetting);

        void RemovePart(AvatarPartHandle partHandle, AvatarAdditionalPartSetting additionalPartSetting);

        AvatarHandle Handle { get; }
    }

    [Implement(typeof(IAvatar))]
    internal sealed class KHBD_Avatar : KHBD_Object, IAvatar, IAvatarInternal {

        private readonly struct BoneName {

            public string Value { get; init; }

            public BoneName(string value) { Value = value; }

            public override readonly string ToString() => Value;

            public override readonly bool Equals(object obj) {
                if (obj is BoneName other) {
                    return other.Value == Value;
                }
                return false;
            }

            public override readonly int GetHashCode() {
                return Value?.GetHashCode() ?? 0;
            }

            public static bool operator ==(BoneName left, BoneName right) => left.Equals(right);

            public static bool operator !=(BoneName left, BoneName right) => !left.Equals(right);

            public static explicit operator string(BoneName @in) => @in.Value;

            public static implicit operator BoneName(string @in) => new(@in);
        }

        class Bone {

            public int UseCount { get; set; }

            public Transform Transform { get; init; }
        }

        class Part {

            public Part(GameObject gameObject) => GameObject = gameObject;

            public GameObject GameObject { get; private set; }

            public AvatarPartHandle Handle { get; private set; } = new AvatarPartHandle();

            public IDictionary<SkinnedMeshRenderer, Transform> RootBoneDictionary { get; private set; } = new Dictionary<SkinnedMeshRenderer, Transform>();

            public void Dispose() {
                Handle = null;
                RootBoneDictionary = null;
                GameObject = null;
            }
        }

        private GameObject m_SkeletonGameObject;
        private IDictionary<BoneName, Bone> m_BoneDictionary = new Dictionary<BoneName, Bone>();
        private IDictionary<AvatarPartHandle, Part> m_PartDictionary = new Dictionary<AvatarPartHandle, Part>();

        public AvatarHandle Handle { get; private set; } = new AvatarHandle();

        Transform IAvatarInternal.SkeletonTransform => m_SkeletonGameObject.transform;

        public void Initialize(GameObject skeletonGameObject) {
            Assert.IsNotNull(skeletonGameObject);

            m_SkeletonGameObject = skeletonGameObject;
            foreach (Transform childTransform in m_SkeletonGameObject.transform) {
                AddBones(childTransform);
            }
        }

        public bool TryGetBone(string boneName, out Transform transform) {
            transform = default;
            if (m_BoneDictionary.TryGetValue(boneName, out var bone)) {
                transform = bone.Transform;
                return true;
            }
            return false;
        }

        private void AddBones(Transform transform) {
            foreach (var boneTransform in transform.GetComponentsInChildren<Transform>()) {
                if (!m_BoneDictionary.ContainsKey(boneTransform.name)) {
                    m_BoneDictionary.Add(boneTransform.name, new Bone { UseCount = 1, Transform = boneTransform });
                } else {
                    Debug.LogWarning($"Duplicate bone name :{boneTransform.name}");
                }
            }
        }

        public AvatarPartHandle AddPart(GameObject partGameObject, AvatarAdditionalPartSetting additionalPartSetting) {
            Assert.IsNotNull(partGameObject);

            //Debug.Log($"{nameof(AddPart)} {partGameObject.name}");
            if (additionalPartSetting == AvatarAdditionalPartSetting.Default) {
                return AddPart(partGameObject);
            } else {
                var bone = m_BoneDictionary[additionalPartSetting.ParentBoneName];
                bone.UseCount++;
                //Debug.Log($"++ bone = {bone.UseCount}, {bone.Transform.name}");
                partGameObject.transform.SetParent(bone.Transform);
                partGameObject.transform.localScale = Vector3.one;
                partGameObject.transform.localPosition = additionalPartSetting.Position;
                partGameObject.transform.localEulerAngles = additionalPartSetting.EulerAngles;
                var part = new Part(partGameObject);
                m_PartDictionary.Add(part.Handle, part);
                var skinnedMeshRenderers = partGameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
                foreach (var skinnedMeshRenderer in skinnedMeshRenderers) {
                    skinnedMeshRenderer.receiveShadows = false;
                    skinnedMeshRenderer.updateWhenOffscreen = true;
                }
                return part.Handle;
            }
        }

        private AvatarPartHandle AddPart(GameObject partGameObject) {
            Assert.IsNotNull(partGameObject);

            var part = new Part(partGameObject);
            m_PartDictionary.Add(part.Handle, part);

            var skinnedMeshRenderers = partGameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
            foreach (var skinnedMeshRenderer in skinnedMeshRenderers) {
                skinnedMeshRenderer.receiveShadows = false;
                skinnedMeshRenderer.updateWhenOffscreen = true;

                part.RootBoneDictionary.Add(skinnedMeshRenderer, skinnedMeshRenderer.rootBone);
                AttachPartBones(skinnedMeshRenderer.rootBone);

                var boneTransforms = skinnedMeshRenderer.bones;
                var newBoneTransforms = new Transform[boneTransforms.Length];

                for (int i = 0; i < boneTransforms.Length; ++i) {
                    var boneTransform = boneTransforms[i];
                    if (m_BoneDictionary.TryGetValue(boneTransform.name, out var bone)) {
                        newBoneTransforms[i] = bone.Transform;
                    } else {
                        Debug.LogError($"[SkinnedMeshRenderer({skinnedMeshRenderer.name})] Unable to map bone [{boneTransform.name}] to target skeleton.");
                    }
                }
                skinnedMeshRenderer.bones = newBoneTransforms;

                // root bone
                if (m_BoneDictionary.ContainsKey(skinnedMeshRenderer.rootBone != null ? skinnedMeshRenderer.rootBone.name : null)) {
                    skinnedMeshRenderer.rootBone = m_BoneDictionary[skinnedMeshRenderer.rootBone.name].Transform;
                }
            }
            return part.Handle;
        }

        public void RemovePart(AvatarPartHandle partHandle, AvatarAdditionalPartSetting additionalPartSetting) {
            Assert.IsNotNull(partHandle);

            if (m_PartDictionary.TryGetValue(partHandle, out var part)) {
                m_PartDictionary.Remove(partHandle);
                //Debug.Log($"{nameof(RemovePart)} {part.GameObject.name}");
                if (additionalPartSetting == AvatarAdditionalPartSetting.Default) {
                    RemovePart(part);
                } else {
                    var bone = m_BoneDictionary[additionalPartSetting.ParentBoneName];
                    bone.UseCount--;
                    //Debug.Log($"-- bone = {bone.UseCount}, {bone.Transform.name}");
                    part.GameObject.transform.SetParent(m_SkeletonGameObject.transform);
                }
                part.Dispose();
            }
        }

        private void RemovePart(Part part) {
            Assert.IsNotNull(part);

            foreach (var kv in part.RootBoneDictionary) {
                DetachPartBones(kv.Value);
            }
        }

        private void AttachPartBones(Transform transform) {
            var bone = m_BoneDictionary[transform.name];
            bone.UseCount++;
            //Debug.Log($"++ bone = {bone.UseCount}, {bone.Transform.name}");
            foreach (Transform childTransform in transform) {
                if (!m_BoneDictionary.ContainsKey(childTransform.name)) {
                    var newChildTransform = UnityEngine.Object.Instantiate(childTransform, bone.Transform);
                    newChildTransform.name = childTransform.name;
                    //Debug.Log($"Add Bones {newChildTransform.name}");
                    AddBones(newChildTransform);
                } else {
                    AttachPartBones(childTransform);
                }
            }
        }

        private void DetachPartBones(Transform transform) {
            foreach (Transform childTransform in transform) {
                DetachPartBones(childTransform);
            }
            var bone = m_BoneDictionary[transform.name];
            bone.UseCount--;
            //Debug.Log($"-- bone = {bone.UseCount}, {bone.Transform.name}");
            if (bone.UseCount == 0) {
                //Debug.Log($"{nameof(DetachPartBones)} {bone.Transform.name}");
                m_BoneDictionary.Remove(bone.Transform.name);
                UnityEngine.Object.Destroy(bone.Transform.gameObject);
            } else if (bone.UseCount < 0) {
                Debug.LogWarning("骨骼引用计数小于0");
            }
        }

        protected override void Dispose() {
            m_BoneDictionary = null;
            m_PartDictionary = null;
            m_SkeletonGameObject = null;
            Handle = AvatarHandle.None;
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Avatar {

    public interface IAvatarInternal {

        Transform SkeletonTransform { get; }

        bool TryGetBone(string boneName, out Transform transform);
    }
}